package se.webbzon.boltzmann.game.npc;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import se.webbzon.boltzmann.game.character.CharacterActionEnum;
import se.webbzon.boltzmann.game.object.player.AbstractPlayerCharacter;
import se.webbzon.boltzmann.npc.AbstractNPC;
import se.webbzon.boltzmann.npc.NPCDuty;
import se.webbzon.oschi01.collision3d.CollisionCube3D;

public class WalkingDuty extends NPCDuty {
	
	// The priority of this duty
	private static final int priority = 0;
	
	// The minimal number of steps to wait between moving
	// to a new position
	private static final int waitSteps = 120;
	
	// The default walking range from the start position
	private static final double defaultRange = 120;
	
	// The maximal range between the non-player character and
	// the player where the non-player character still ignores the
	// player
	private static final double defaultIgnoreRange = 64;

	// The maximal walking range from the start position
	private final double range;
	
	// The ignore range for this non-player character duty
	private final double ignoreRange;
	
	// The target destination
	private final Point3d target;
	
	// A counter
	private int counter;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new walking duty with the default walking range. **/
	public WalkingDuty(AbstractNPC npc) {
		this(npc,defaultRange,defaultIgnoreRange);
	}
	
	/** Creates a new walking duty with a given maximal walking range. **/
	public WalkingDuty(AbstractNPC npc, double range, double ignoreRange) {
		super(priority,npc);
		this.range = range;
		this.ignoreRange = ignoreRange;
		target = new Point3d();
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the maximal walking range from the start position. **/
	public double getRange() {
		return range;
	}
	
	/** Returns the ignore range for this duty. **/
	public double getIgnoreRange() {
		return ignoreRange;
	}
	
	/** Sets the walking target for this duty together with a motion speed. **/
	public void setTarget(double x, double z, double speed) {
		if (getNPC().getDuty() == this && (target.x != x || target.z != z)) {
			target.set(x,target.y,z);
			AbstractNPC npc = getNPC();
			final double angle = Math.atan2(npc.getZ() - z,
											x - npc.getX());
			npc.setVelocity(speed * Math.cos(angle),0,-speed * Math.sin(angle));
			npc.setAction(CharacterActionEnum.WALK);
			if (angle > 0)
				npc.setFacing(angle);
			else
				npc.setFacing(2 * Math.PI + angle);
			counter = -1;
		}
	}
	
	@Override public void setOrigin(double x, double y, double z) {
		super.setOrigin(x, y, z);
		AbstractNPC npc = getNPC();
		if (npc != null)
			setTarget(x,z,npc.getWalkSpeed());
	}
	
	@Override public void onBegin() {
		AbstractNPC npc = getNPC();
		counter = waitSteps;
		npc.setAction(CharacterActionEnum.STAND);
	}

	@Override public void onStep() {
		AbstractNPC npc = getNPC();
		GameNPC gameNPC = npc instanceof GameNPC ? (GameNPC) npc : null;
		
		if (gameNPC != null) {
		
			if (!gameNPC.getPlayers().isEmpty()) {
				AbstractPlayerCharacter player = gameNPC.closestPlayer();
			
				if (npc.distanceTo(player) < ignoreRange) {
					// Stop and look at player
					npc.setVelocity(0, 0, 0);
					npc.setFacing(player);
					npc.setAction(CharacterActionEnum.STAND);
					counter = waitSteps;
					return; 
					// Do nothing more
				}
			}
		}
		
		
		if (counter > 0)
			// Wait for a bit
			counter--;
		
		else if (counter == 0) {
			Point3d origin = new Point3d();
			getOrigin(origin);
			setTarget(	origin.x + range*(Math.random()-0.5),
						origin.z + range*(Math.random()-0.5),
						npc.getWalkSpeed());
		} else {
			// Check if the non-player character has reached the target destination
			final double dx = target.x - npc.getX();
			final double dz = target.z - npc.getZ();
			if (-2 < dx && dx < 2 && -2 < dz && dz < 2)
				onReachTarget();
			else if (gameNPC != null && npc.getMask() != null) {
				if (!motionFree(gameNPC))
					onBlockedPath();
			}
		}
	}

	@Override public void onEnd() {
		getNPC().setVelocity(0, 0, 0);
	}
	
	/*============================================================
	Protected Methods
	============================================================*/
	
	/** Returns true if the current motion is collision free for the given
	 * non-player game character. **/
	protected static boolean motionFree(GameNPC npc) {
		Vector3d position = npc.getTranslation();
		position.x += npc.getXVelocity();
		position.y += npc.getYVelocity();
		position.z += npc.getZVelocity();
	
		npc.setSolid(true);
		final boolean motionFree = npc.getWorld().placeFree(npc, position, null, null);
		npc.setSolid(false);
		return motionFree;
	}
	
	/*============================================================
	Protected Methods
	============================================================*/
	
	/** This method is invoked when the the non-player character associated
	 * with this duty reaches the target. **/
	protected void onReachTarget() {
		AbstractNPC npc = getNPC();
		npc.setVelocity(0, 0, 0);
		counter = waitSteps + (int) (waitSteps * Math.random());
		npc.setAction(CharacterActionEnum.STAND);
	}
	
	/** This method is invoked when the the non-player character associated
	 * with this has a blocked motion path. **/
	protected void onBlockedPath() {
		AbstractNPC npc = getNPC();
		npc.setVelocity(0, 0, 0);
		counter = waitSteps + (int) (waitSteps * Math.random());
		npc.setAction(CharacterActionEnum.STAND);
	}

}
